package planning.heuristic;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import planning.util.MachineUsageData;
import planning.util.PlanIOHandler;
import provisioning.DPS;
import provisioning.Monitor;
import provisioning.OptimalProvisioningSystemForHeterogeneousMachines;

import commons.cloud.MachineType;
import commons.cloud.Provider;
import commons.cloud.User;
import commons.config.Configuration;
import commons.io.Checkpointer;
import commons.sim.SimpleSimulator;
import commons.sim.components.LoadBalancer;
import commons.sim.jeevent.JEEventScheduler;
import commons.sim.util.SimulatorProperties;

/**
 * This class represents the heuristic that plans reservation evaluating machine utilization
 * over a planning period (e.g., one year)
 * @author David Candeia
 *
 */
public class IosupBasedHeuristic implements PlanningHeuristic{

	private Map<Provider, Map<MachineType, Integer>> plan;	
	private MachineUsageData machineData;
	private final Monitor monitor;

	private Map<MachineType, Map<Long, Double>> hoursUsed;//map used to save CPU consumption information during the planning period
	private HashMap<MachineType, Integer> lightReservation;
	private HashMap<MachineType, Integer> mediumReservation;
	private HashMap<MachineType, Integer> heavyReservation;

	/**
	 * Default constructor. 
	 * @param scheduler Event scheduler
	 * @param monitor Application monitor (DPS)
	 * @param loadBalancers Load balancers used in simulation
	 */
	public IosupBasedHeuristic(JEEventScheduler scheduler, Monitor monitor, LoadBalancer[] loadBalancers){
		this.monitor = monitor;
		
		this.plan = new HashMap<Provider, Map<MachineType,Integer>>();
		
		try {
			machineData = PlanIOHandler.getMachineData();
		} catch (IOException e) {
		} catch (ClassNotFoundException e) {
		}
		if(machineData == null){
			machineData = new MachineUsageData();
		}
		
		this.hoursUsed = new HashMap<MachineType, Map<Long,Double>>();
		this.lightReservation = new HashMap<MachineType, Integer>();
		this.mediumReservation = new HashMap<MachineType, Integer>();
		this.heavyReservation = new HashMap<MachineType, Integer>();
	}
	
	@Override
	public void findPlan(Provider[] cloudProviders, User[] cloudUsers) {
		
		//Simulating ...
//		DPS dps = (DPS) this.monitor;
//		
//		SimpleSimulator simulator = (SimpleSimulator) Checkpointer.loadApplication();
//		
//		dps.registerConfigurable(simulator);
//		
//		simulator.start();
//		
//		//Calculating machines use data
//		LoadBalancer[] loadBalancers = simulator.getTiers(); 
//		calculateMachinesUsage();
		Configuration config = Configuration.getInstance();
//		
//		if(Checkpointer.loadSimulationInfo().isFinishDay()){//Simulation finished!
			
			//Creates reservation plan
			calculateMachinesToReserve(config);
			
			Checkpointer.clear();
			PlanIOHandler.clear();
			try {
				PlanIOHandler.createPlanFile(this.plan);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
//		}else{//Persist data to other round
//			persisDataToNextRound(loadBalancers, config);
//		}
	}
	
	/**
	 * This method calculates the utilization target at which each machine type reservation is profitable
	 * @param providers IaaS provider
	 * @param planningPeriod Period being planned 
	 * @return Percentage of minimum utilization for each machine type
	 */
	private Map<Provider, Map<MachineType, Double>> findReservationTargets(Provider[] providers, long planningPeriod) {
		Map<Provider, Map<MachineType, Double>> targets = new HashMap<Provider, Map<MachineType,Double>>();
		
		for(Provider provider : providers){
			MachineType[] machineTypes = provider.getAvailableTypes();
			Map<MachineType, Double> typesLimits = new HashMap<MachineType, Double>();//utilization targets per machine type
			
			for(MachineType type : machineTypes){
				double yearFee = provider.getReservationOneYearFee(type);
				double reservedCpuCost = provider.getReservedCpuCost(type);
				double onDemandCpuCost = provider.getOnDemandCpuCost(type);
				
				long minimumHoursToBeUsed = Math.round(yearFee / (onDemandCpuCost - reservedCpuCost));
				double usageProportion = 1.0 * minimumHoursToBeUsed / (planningPeriod * 24);
				
				typesLimits.put(type, usageProportion);
			}
			targets.put(provider, typesLimits);
		}
		
		return targets;
	}
	
	/**
	 * This method looks for an amount of machines to be reserved in different. Firstly, the heuristic builds a reservation plan for each provider
	 * independently and then it merges all plans
	 * @param config
	 */
	private void calculateMachinesToReserve(Configuration config) {
		
		//Sorting providers
		Provider[] providers = sortProviders();
		Provider lightProvider = providers[0];
		Provider mediumProvider = providers[1];
		Provider heavyProvider = providers[2];
		
		Map<MachineType, Map<Long, Double>> machineUsagePerType = this.machineData.getMachineUsagePerType();
		int lightToReserve = 0;
		int mediumToReserve = 0;
		int heavyToReserve = 0;
		double efficientRatioLight = lightProvider.getReservationOneYearFee(MachineType.M1_SMALL) * 1.0 / (lightProvider.getOnDemandCpuCost(MachineType.M1_SMALL) - 
				lightProvider.getReservedCpuCost(MachineType.M1_SMALL)); //Light market
		double efficientRatioMedium = mediumProvider.getReservationOneYearFee(MachineType.M1_SMALL) * 1.0 / (mediumProvider.getOnDemandCpuCost(MachineType.M1_SMALL) - 
				mediumProvider.getReservedCpuCost(MachineType.M1_SMALL)); //Medium market
		double efficientRatioHeavy = heavyProvider.getReservationOneYearFee(MachineType.M1_SMALL) * 1.0 / (heavyProvider.getOnDemandCpuCost(MachineType.M1_SMALL) - 
				heavyProvider.getReservedCpuCost(MachineType.M1_SMALL)); //Heavy market
		long planningPeriod = config.getLong(SimulatorProperties.PLANNING_PERIOD);
		efficientRatioLight /= (24 * planningPeriod);
		efficientRatioMedium /= (24 * planningPeriod);
		efficientRatioHeavy /= (24 * planningPeriod);
		
		double minCost = 0d;
		double checkedCost = Double.MAX_VALUE;
		
		for(MachineType type : machineUsagePerType.keySet()){
			Map<Long, Double> amountOfMachinesHoursConsumption = machineUsagePerType.get(type);
			List<Long> amountOfMachinesHistory = new ArrayList<Long>(amountOfMachinesHoursConsumption.keySet());
			java.util.Collections.sort(amountOfMachinesHistory);
			
			int maxVal = new Long(amountOfMachinesHistory.get(amountOfMachinesHistory.size()-1)).intValue();//Maxium amount of machines used
			
			int probability[] = new int[maxVal + 1]; 
			for(Entry<Long, Double> entry : amountOfMachinesHoursConsumption.entrySet()){
				int amountOfMachines = new Long(entry.getKey()).intValue();
				double amountOfHours = entry.getValue();
				
				probability[amountOfMachines] += amountOfHours;
			}
			
			int upperBound = new Long (amountOfMachinesHistory.get((int)((1-efficientRatioLight) * amountOfMachinesHistory.size()))).intValue();//Planning upper bound
			minCost = Double.MAX_VALUE;
			double denominor = 1.0;
			
			//Creating map of plans considering the ones that might be skipped
//			HashMap<String, Double> values = new HashMap<String, Double>();
			upperBound = upperBound + 20;
			int totalSpace = (upperBound + 1) * (upperBound + 1) * (upperBound + 1);
//			for(int n1 = 0; n1 <= upperBound; n1++){
//				for(int n2 = 0; n2 <= upperBound; n2++){
//					for(int n3=0; n3 <= upperBound; n3++){
//						String key = n1 + "-" + n2 + "-" + n3;
//						values.put(key, 0.0);
//					}
//				}
//			}
			
			int numberOfSkip = 0;//Number of plans to skip
			for(int currentLight = 0; currentLight <= upperBound; currentLight++){
				for(int currentMedium = 0; currentMedium <= upperBound; currentMedium++){
					for(int currentHeavy = 0; currentHeavy <= upperBound; currentHeavy++){
						
//						String key = currentLight + "-" + currentMedium + "-" + currentHeavy;
//						double value = values.get(key);
//						if(value == Double.MAX_VALUE){
//							numberOfSkip ++;
//							continue;
//						}

						//Reservation cost and heavy market usage
						double cost = currentLight * lightProvider.getReservationOneYearFee(MachineType.M1_SMALL) + 
								currentMedium * mediumProvider.getReservationOneYearFee(MachineType.M1_SMALL) + 
								currentHeavy * (heavyProvider.getReservationOneYearFee(MachineType.M1_SMALL) + (planningPeriod * 24) * heavyProvider.getReservedCpuCost(MachineType.M1_SMALL));
						
						//Usage cost at medium market
						for(int d = currentHeavy + 1; d <= currentHeavy + currentMedium && d <= maxVal; d++){
							int extraResourcesNeeded = d-currentHeavy;
							cost += probability[d] * (mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * extraResourcesNeeded) / denominor;
						}
						
						//Usage cost at medium and light market
						for(int d = currentMedium + currentHeavy + 1; d <= currentHeavy + currentMedium + currentLight  && d <= maxVal; d++){//light usage
							cost += (probability[d] * ( mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * currentMedium 
									+ lightProvider.getReservedCpuCost(MachineType.M1_SMALL) * (d - (currentMedium + currentHeavy)))) / denominor;
						}
						
						//Usage cost at on-demand market
						for(int d = currentHeavy + currentMedium + currentLight + 1; d <= maxVal; d++){//on-demand usage
							cost += (probability[d] * ( (d - currentLight - currentMedium - currentHeavy) * lightProvider.getOnDemandCpuCost(MachineType.M1_SMALL)
									+ mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * currentMedium + lightProvider.getReservedCpuCost(MachineType.M1_SMALL) * currentLight)) / denominor;
						}
						
//						values.put(key, cost);
	//					System.out.println(n1 + ", " + n2 + ", " + n3 + " : " + cost);
						if(minCost > cost){
							minCost = cost;
							lightToReserve = currentLight;
							mediumToReserve = currentMedium;
							heavyToReserve = currentHeavy;
						}
						
						//get the value of last cost and then cut them
//						int n1_ = currentLight -1;
//						int n2_ = currentMedium - 1;
//						int n3_ = currentHeavy -1;
//						if(n1_ >= 0){
//							String k = n1_ + "-" + currentMedium + "-" +currentHeavy;
//							double v = values.get(k);
//							if(v != 0.0 && cost > v){
//								for(int k1 = currentLight + 1; k1 <= upperBound; k1++){
//									for(int k2 = currentMedium; k2 <= upperBound; k2++){
//										for(int k3 = currentHeavy; k3 <= upperBound; k3++){
//											String theKey = k1 + "-" + k2 + "-" + k3;
//											values.put(theKey, Double.MAX_VALUE);
//										}
//									}
//								}
//							}
//						}
//						if(n2_ >= 0){
//							String k = currentLight + "-" + n2_ + "-" +currentHeavy;
//							double v = values.get(k);
//							if(v != 0.0 && cost > v){
//								for(int k1 = currentLight; k1 <= upperBound; k1++){
//									for(int k2 = currentMedium + 1; k2 <= upperBound; k2++){
//										for(int k3= currentHeavy; k3 <= upperBound; k3++){
//											String theKey = k1 + "-" + k2 + "-" + k3;
//											values.put(theKey, Double.MAX_VALUE);
//										}
//									}
//								}
//							}
//						}
//						if(n3_ >= 0){
//							String k = currentLight + "-" + currentMedium + "-" +n3_;
//							double v = values.get(k);
//							if(v != 0.0 && cost > v){
//								for(int k1 = currentLight; k1 <= upperBound; k1++){
//									for(int k2 = currentMedium; k2 <= upperBound; k2++){
//										for(int k3 = currentHeavy + 1; k3 <= upperBound; k3++){
//											String theKey = k1 + "-" + k2 + "-" + k3;
//											values.put(theKey, Double.MAX_VALUE);
//										}
//									}
//								}
//							}
//						}
					}
				}
			}
//			if(isOutputInReservedInstances){
				System.err.println(numberOfSkip + " skip out of " + totalSpace + " " + (numberOfSkip*1.0/totalSpace *100.0) +"%");
				System.err.println("The cost obtained by brute-2 is " + minCost + " : " + lightToReserve + ", " + mediumToReserve + ", " + heavyToReserve);
//			}
//			numberOfMachineAtEachInterval.clear();

			//Checking usage of other machine types in current reservation plan
			checkedCost = checkOtherTypes(planningPeriod, lightProvider, mediumProvider, heavyProvider, lightToReserve, mediumToReserve, heavyToReserve, maxVal, probability, minCost);
		}
		
		if(minCost <= checkedCost){
			lightReservation.clear();
			mediumReservation.clear();
			heavyReservation.clear();
			
			lightReservation.put(MachineType.M1_SMALL, lightToReserve);
			mediumReservation.put(MachineType.M1_SMALL, mediumToReserve);
			heavyReservation.put(MachineType.M1_SMALL, heavyToReserve);
		}
		//Building plan
		createPlan(providers);
	}

	/**
	 * This method tries to check if it is worth changing small reserved resources in each market for large or xlarge resources
	 * @param lightProvider
	 * @param mediumProvider
	 * @param heavyProvider
	 * @param lightToReserve
	 * @param mediumToReserve
	 * @param heavyToReserve
	 * @param maxVal
	 * @param probability
	 * @param minCost
	 * @return
	 */
	private double checkOtherTypes(long planningPeriod, Provider lightProvider, Provider mediumProvider, Provider heavyProvider, int lightToReserve, int mediumToReserve, int heavyToReserve, int maxVal, int[] probability, double minCost) {
		int [] lightAmounts = new int[2];
		lightAmounts[0] = lightToReserve / 4;
		lightAmounts[1] = lightToReserve / 8;
		
		int [] mediumAmounts = new int[2];
		mediumAmounts[0] = mediumToReserve / 4;
		mediumAmounts[1] = mediumToReserve / 8;
		
		int [] heavyAmounts = new int[2];
		heavyAmounts[0] = heavyToReserve / 4;
		heavyAmounts[1] = heavyToReserve / 8;
		
		double currentMin = minCost;
		
		for(int lightIndex = 0; lightIndex < lightAmounts.length; lightIndex++){
			for(int mediumIndex = 0; mediumIndex < mediumAmounts.length; mediumIndex++){
				for(int heavyIndex = 0; heavyIndex < heavyAmounts.length; heavyIndex++){
					
					int lightCores = lightAmounts[lightIndex] * (lightIndex+1) * 4;
					int lightSmall = Math.max(lightToReserve - lightCores, 0);
					int mediumCores = mediumAmounts[mediumIndex] * (mediumIndex+1) * 4;
					int mediumSmall = Math.max(mediumToReserve - mediumCores, 0);
					int heavyCores = heavyAmounts[heavyIndex] * (heavyIndex+1) * 4;
					int heavySmall = Math.max(heavyToReserve - heavyCores, 0);
					
					//Light market reservation fees
					double lightCost = lightSmall * lightProvider.getReservationOneYearFee(MachineType.M1_SMALL);//Small reserved
					if(lightIndex == 0){//large reserved
						lightCost += lightAmounts[lightIndex] * lightProvider.getReservationOneYearFee(MachineType.M1_LARGE);
					}else{//xlarge reserved
						lightCost += lightAmounts[lightIndex] * lightProvider.getReservationOneYearFee(MachineType.M1_XLARGE);
					}
					
					//Medium market reservation fees
					double mediumCost = mediumSmall * mediumProvider.getReservationOneYearFee(MachineType.M1_SMALL);//Small reserved
					if(mediumIndex == 0){//large reserved
						mediumCost += mediumAmounts[mediumIndex] * mediumProvider.getReservationOneYearFee(MachineType.M1_LARGE);
					}else{//xlarge reserved
						mediumCost += mediumAmounts[mediumIndex] * mediumProvider.getReservationOneYearFee(MachineType.M1_XLARGE);
					}
					
					//Heavy market reservation fees
					double heavyCost = heavySmall * (heavyProvider.getReservationOneYearFee(MachineType.M1_SMALL) + (planningPeriod * 24) * heavyProvider.getReservedCpuCost(MachineType.M1_SMALL));//Small reserved
					if(heavyIndex == 0){//large reserved
						heavyCost += heavyAmounts[heavyIndex] * (heavyProvider.getReservationOneYearFee(MachineType.M1_LARGE) + (planningPeriod * 24) * heavyProvider.getReservedCpuCost(MachineType.M1_LARGE));
					}else{//xlarge reserved
						heavyCost += heavyAmounts[heavyIndex] * (heavyProvider.getReservationOneYearFee(MachineType.M1_XLARGE) + (planningPeriod * 24) * heavyProvider.getReservedCpuCost(MachineType.M1_XLARGE));
					}
					double cost = lightCost + mediumCost + heavyCost;
					
					//Usage cost at medium market
					int mediumNewTypeAmount = 0;
					int mediumSmallAmount = 0;
					for(int d = heavyToReserve + 1; d <= heavyToReserve + mediumToReserve && d <= maxVal; d++){
						int amountToProcess = d - heavyToReserve;
						mediumNewTypeAmount = Math.min(mediumAmounts[mediumIndex], (int)Math.ceil((amountToProcess * 1.0) / ((mediumIndex+1) * 4)));
						mediumSmallAmount = Math.max(0,  amountToProcess - mediumNewTypeAmount * ((mediumIndex+1) * 4));
						
						if(mediumIndex == 0){//large
//							int amountOfHours = probability[d] * ((mediumNewTypeAmount * (mediumIndex+1) * 4) / (amountToProcess));
							cost += Math.ceil(probability[d] / 4.0) * (mediumProvider.getReservedCpuCost(MachineType.M1_LARGE) * mediumNewTypeAmount) / 1.0;
						}else{//xlarge
//							int amountOfHours = probability[d] * ((mediumNewTypeAmount * (mediumIndex+1) * 4) / (amountToProcess));
							cost += Math.ceil(probability[d] / 8.0) * (mediumProvider.getReservedCpuCost(MachineType.M1_XLARGE) * mediumNewTypeAmount) / 1.0;
						}
						
//						int smallAmountHours = probability[d] * (mediumSmallAmount / (amountToProcess));
						cost += probability[d] * (mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * mediumSmallAmount) / 1.0;
					}
					
					//Usage cost at medium and light market
					int lightNewTypeAmount = 0;
					int lightSmallAmount = 0;
					for(int d = heavyToReserve + mediumToReserve + 1; d <= heavyToReserve + mediumToReserve + lightToReserve  && d <= maxVal; d++){//light usage
						int amountToProcess = d - (heavyToReserve + mediumToReserve);
						lightNewTypeAmount = Math.min(lightAmounts[mediumIndex], (int) Math.ceil((amountToProcess * 1.0) / ((lightIndex+1) * 4)));
						lightSmallAmount = Math.max(0,  amountToProcess - lightNewTypeAmount * ((lightIndex+1) * 4));
						
						//Medium consumption
						if(mediumIndex == 0){//large
							cost += Math.ceil(probability[d] / 4.0) * (mediumProvider.getReservedCpuCost(MachineType.M1_LARGE) * mediumNewTypeAmount) / 1.0;
						}else{//xlarge
							cost += Math.ceil(probability[d] / 8.0) * (mediumProvider.getReservedCpuCost(MachineType.M1_XLARGE) * mediumNewTypeAmount) / 1.0;
						}
						cost += probability[d] * (mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * mediumSmallAmount) / 1.0;
						
						//Light consumption
						if(lightIndex == 0){//large
							int amountOfHours = probability[d];
							cost += Math.ceil(amountOfHours / 4.0) * (lightProvider.getReservedCpuCost(MachineType.M1_LARGE) * lightNewTypeAmount) / 1.0;
						}else{//xlarge
							int amountOfHours = probability[d];
							cost += Math.ceil(amountOfHours / 8.0) * (lightProvider.getReservedCpuCost(MachineType.M1_XLARGE) * lightNewTypeAmount) / 1.0;
						}
						
						cost += probability[d] * (lightProvider.getReservedCpuCost(MachineType.M1_SMALL) * lightSmallAmount)/1.0;
					}
					
					//Usage cost at on-demand market
					for(int d = heavyToReserve + mediumToReserve + lightToReserve + 1; d <= maxVal; d++){//on-demand usage
						//Medium consumption
						if(mediumIndex == 0){//large
							cost += probability[d] / 4 * (mediumNewTypeAmount * mediumProvider.getReservedCpuCost(MachineType.M1_LARGE));
						}else{//xlarge
							cost += probability[d] / 8 * (mediumProvider.getReservedCpuCost(MachineType.M1_XLARGE) * mediumNewTypeAmount) / 1.0;
						}
						cost += probability[d] * (mediumProvider.getReservedCpuCost(MachineType.M1_SMALL) * mediumSmallAmount)/1.0;
						
						//Light consumption
						if(lightIndex == 0){//large
							int amountOfHours = probability[d];
							cost += amountOfHours / 4 * (lightProvider.getReservedCpuCost(MachineType.M1_LARGE) * lightNewTypeAmount) / 1.0;
						}else{//xlarge
							int amountOfHours = probability[d];
							cost += amountOfHours / 8 * (lightProvider.getReservedCpuCost(MachineType.M1_XLARGE) * lightNewTypeAmount) / 1.0;
						}
						cost += probability[d] * (lightProvider.getReservedCpuCost(MachineType.M1_SMALL) * lightSmallAmount)/1.0;
						
						//On-demand consumption
						cost += probability[d] * ((d - lightToReserve - mediumToReserve - heavyToReserve) * lightProvider.getOnDemandCpuCost(MachineType.M1_SMALL));
					}
					
					if(currentMin > cost){
						currentMin = cost;
						lightReservation = new HashMap<MachineType, Integer>();
						if(lightIndex == 0){//Large instances
							lightReservation.put(MachineType.M1_LARGE, lightAmounts[lightIndex]);
						}else{//Xlarge instances
							lightReservation.put(MachineType.M1_XLARGE, lightAmounts[lightIndex]);
						}
						lightReservation.put(MachineType.M1_SMALL, lightSmall);
						
						mediumReservation = new HashMap<MachineType, Integer>();
						if(mediumIndex == 0){//Large instances
							mediumReservation.put(MachineType.M1_LARGE, mediumAmounts[mediumIndex]);
						}else{//Xlarge instances
							mediumReservation.put(MachineType.M1_XLARGE, mediumAmounts[mediumIndex]);
						}
						mediumReservation.put(MachineType.M1_SMALL, mediumSmall);
						
						heavyReservation = new HashMap<MachineType, Integer>();
						if(heavyIndex == 0){//Large instances
							heavyReservation.put(MachineType.M1_LARGE, heavyAmounts[heavyIndex]);
						}else{//Xlarge instances
							heavyReservation.put(MachineType.M1_XLARGE, heavyAmounts[heavyIndex]);
						}
						heavyReservation.put(MachineType.M1_SMALL, heavySmall);
					}
				}
			}
		}
		
		return currentMin;
	}
	
	/**
	 * Creating reservation plan
	 * @param providers
	 */
//	private void createPlan(Provider[] providers, int lightToReserve, int mediumToReserve, int heavyToReserve) {
	private void createPlan(Provider[] providers) {
		
		if(!lightReservation.isEmpty()){
			Map<MachineType, Integer> reservationForCurrentProvider = this.plan.get(providers[0]);
			if(reservationForCurrentProvider == null){
				reservationForCurrentProvider = new HashMap<MachineType, Integer>();
			}
			boolean addToPlan = false;
			for(MachineType type : lightReservation.keySet()){
				Integer toReserve = lightReservation.get(type);
				reservationForCurrentProvider.put(type, toReserve);
				if(toReserve > 0){
					addToPlan = true;
				}
			}
			if(addToPlan){
				this.plan.put(providers[0], reservationForCurrentProvider);
			}
		} 
		
		if(!mediumReservation.isEmpty()){
			Map<MachineType, Integer> reservationForCurrentProvider = this.plan.get(providers[1]);
			if(reservationForCurrentProvider == null){
				reservationForCurrentProvider = new HashMap<MachineType, Integer>();
			}
			boolean addToPlan = false;
			for(MachineType type : mediumReservation.keySet()){
				Integer toReserve = mediumReservation.get(type);
				reservationForCurrentProvider.put(type, mediumReservation.get(type));
				if(toReserve > 0){
					addToPlan = true;
				}
			}
			if(addToPlan){
				this.plan.put(providers[1], reservationForCurrentProvider);
			}
		} 
		
		if(!heavyReservation.isEmpty()){
			Map<MachineType, Integer> reservationForCurrentProvider = this.plan.get(providers[2]);
			if(reservationForCurrentProvider == null){
				reservationForCurrentProvider = new HashMap<MachineType, Integer>();
			}
			boolean addToPlan = false;
			for(MachineType type : heavyReservation.keySet()){
				Integer toReserve = heavyReservation.get(type);
				reservationForCurrentProvider.put(type, heavyReservation.get(type));
				if(toReserve > 0){
					addToPlan = true;
				}
			}
			if(addToPlan){
				this.plan.put(providers[2], reservationForCurrentProvider);
			}
		}
	}

	/**
	 * This method sorts providers according to their minimum utilization targets
	 * @return An array containing providers ordered according to their minimum utilization targets 
	 */
	private Provider[] sortProviders() {
		Provider[] providers = Checkpointer.loadProviders();
		Arrays.sort(providers);
				
		return providers;
	}

	/**
	 * Saving current simulation day information
	 * @param loadBalancers
	 * @param config
	 */
	private void persisDataToNextRound(LoadBalancer[] loadBalancers,
			Configuration config) {
		try {
			Checkpointer.save();
			Checkpointer.dumpMachineData(this.machineData);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Retrieving amount of machines used by DPS.
	 */
	private void calculateMachinesUsage() {
		
		LinkedList<Integer> usage = ((OptimalProvisioningSystemForHeterogeneousMachines) this.monitor).machinesPerHour;
		for(Integer numberOfMachines : usage){
			machineData.addUsage(MachineType.M1_SMALL, numberOfMachines);//Storing amount of machines used and its related amount of hours
		}
	}

	@Override
	public double getEstimatedProfit(int period) {
		return 0;
	}

	@Override
	public Map<Provider, Map<MachineType, Integer>> getPlan(User[] cloudUsers) {
		return this.plan;
	}
}
